using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq.Expressions;
using System.Reflection;
using FastReflectionLib;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.EnterpriseLibrary.Data.Sql;

namespace HF.Repository.MSSql
{
    public abstract class ReadonlyRepository<T> : IReadonlyRepository<T> where T : ReadonlyRepositoryModel, new()
    {
        private const string select = "select *  from [{0}]  {1} {2}";
        private const string SelectCount = "select count(*)  from [{0}]  {1}";
        protected static ICacheManager _cacheManager;

        protected SqlDatabase sqldb;
     //   protected SqlExpressionParser sqlExpressionParser;
        protected string tableName;
        protected ReadonlyRepository(string tableName)
        {
            this.tableName = tableName;
          //  sqlExpressionParser = new SqlExpressionParser();
            sqldb = SqlRepositoryHelper.CreateDatabase();
            if (EnableCache)
            {
                _cacheManager = SqlRepositoryHelper.GetCacheManager();
            }
        }

        static bool EnableCache
        {
            get
            {
                bool enable;
                bool.TryParse(ConfigurationManager.AppSettings["EnableSqlRepositoryKeyCache"], out enable);
                return enable;
            }
        }

        #region IReadonlyRepository<T> Members

        public bool Exists(T model)
        {
            return Exists(model, null);
        }

        public bool Exists(T model, IDbTransaction transaction)
        {
            object key = model.GetKeyValue();
            if (EnableCache && CacheExists(key.ToString()))
            {
                return true;
            }
            return Count(p => p.GetKeyValue() == key, transaction) > 0;
        }

        public T Get<TKey>(TKey key) where TKey : IEquatable<TKey>
        {
            return Get(key,null);
        }
        public T Get<TKey>(TKey key, IDbTransaction transaction) where TKey : IEquatable<TKey>
        {
            Func<T> setter = () =>
                                 {
                                     var sql = string.Format(select, tableName,
                                                             string.Format("where [{0}] = {1}",
                                                                           RepositoryReflectionCache.
                                                                               GetKeyName(typeof (T)),
                                                                           key.ToSqlValue()),
                                                             string.Empty);
                                     using (var reader = ExecuteReader(sqldb, transaction,
                                                                             sql))
                                     {
                                         T model = null;
                                         if (reader.Read())
                                             model = ToModel(reader);
                                         return model;
                                     }
                                 };


            if (EnableCache)
            {
                string sKey = key.ToString();
                CacheGetter<T> getter = (out T p) =>
                                            {
                                                p = CacheGet(sKey);
                                                return p != null;
                                            };
                T t = CacheHelper.Get(getter, setter, AddToCacheIfEnabled);
                return t;
            }

            return setter();
        }

        public T Get(Expression<Predicate<T>> where)
        {
            var sql = new SqlExpressionParser().GetSqlStatement(where);
            
            if (string.IsNullOrEmpty(sql))
            {
                return default(T);
            }
            sql = string.Format(select, tableName,
                                string.Format("where {0}", sql
                                    ),
                                string.Empty);

            using (var reader = sqldb.ExecuteReader(CommandType.Text,
                                                            sql))
            {
                T model = null;
                if (reader.Read())
                    model = ToModel(reader);
                return model;
            }
        }

        public int Count()
        {
            return Count(p => true);
        }

        public int Count(Expression<Predicate<T>> where)
        {
            return Count(where, null);
        }

        public int Count(Expression<Predicate<T>> where, IDbTransaction transaction)
        {
            var wheresql = new SqlExpressionParser().GetSqlStatement(where);
            var sql = string.Format(SelectCount, tableName, string.Empty);

            if (wheresql.Length > 0)
            {
                sql = string.Format("{0} where {1}", sql, wheresql);
            }
            var value = ExecuteScalar(sqldb, transaction, sql);
            var count = int.Parse(value.ToString());
            return count;
        }


        public PagedList<T> Select(int index, int pageSize)
        {
            return Select(index, pageSize, p => true);
        }

        public PagedList<T> Select(int index, int pageSize, Expression<Predicate<T>> where)
        {
            return Select<object>(index, pageSize, where, null);
        }

        public PagedList<T> Select<TKey>(int index, int pageSize, Expression<Predicate<T>> where,
                                         Expression<Func<T, TKey>> order)
        {
            return Select(index, pageSize, where, order, true);
        }

        public PagedList<T> Select<TKey>(int index, int pageSize, Expression<Predicate<T>> where,
                                         Expression<Func<T, TKey>> order, bool asc)
        {
            string wheresql = new SqlExpressionParser().GetSqlStatement(where);
            string ordersql = new SqlExpressionParser().GetSqlStatement(order);

            var p8 = new SqlParameter
                         {
                             ParameterName = "@PageCount",
                             Direction = ParameterDirection.InputOutput,
                             SqlDbType = SqlDbType.Int,
                             Size = 4,
                             Value = 0
                         };
            var p9 = new SqlParameter
                         {
                             ParameterName = "@TotalCount",
                             Direction = ParameterDirection.InputOutput,
                             SqlDbType = SqlDbType.Int,
                             Size = 4,
                             Value = 0
                         };
            var parameters = new[]
                                 {
                                     new SqlParameter("@tbname", string.Format("[{0}]", tableName)),
                                     new SqlParameter("@FieldKey", RepositoryReflectionCache.GetKeyName(typeof (T))),
                                     new SqlParameter("@PageCurrent", index),
                                     new SqlParameter("@PageSize", pageSize),
                                     new SqlParameter("@FieldShow", "*"),
                                     new SqlParameter("@FieldOrder",
                                                      ordersql.Length > 0 ? (asc ? " asc " : " desc ") : ""),
                                     new SqlParameter("@Where", wheresql),
                                     p8, p9
                                 };
            var list = new List<T>();
            using (var cmd = sqldb.GetStoredProcCommand("[sp_PageView]"))
            {
                cmd.Parameters.AddRange(parameters);
                using (var reader = sqldb.ExecuteReader(cmd))
                {
                    while (reader.Read())
                    {
                        list.Add(ToModel(reader));
                    }
                    reader.Close();
                }
            }
            return list.ToPagedList(index, pageSize, int.Parse(p9.Value.ToString()));
        }

        #endregion

        protected void AddToCacheIfEnabled(T model)
        {
            if (!EnableCache || model == null) return;
            var key = model.GetKeyValue().ToString();
            _cacheManager.Add(GetCacheKey(key), model, CacheItemPriority.Normal, null,
                              new SlidingTime(TimeSpan.FromMinutes(5)));
        }

        protected void RemoveFromCacheIfEnabled(T model)
        {
            if (!EnableCache || model == null) return;
            string key =model.GetKeyValue().ToString();
            RemoveFromCacheIfEnabled(key);
        }
        protected void RemoveFromCacheIfEnabled(string key)
        {
            if (!EnableCache) return;
            _cacheManager.Remove(GetCacheKey(key));
        }

        protected T CacheGetReadonly(string key)
        {
            if (!EnableCache) return default(T);
            var obj = _cacheManager.GetData(GetCacheKey(key)) as T;

            return obj;
        }
        protected T CacheGet(string key)
        {
            var obj = CacheGetReadonly(key);

            if (null != obj)
            {
                return obj.MemberwiseClone() as T;
            }
            return default(T);
        }
        protected bool CacheExists(string key)
        {
            return EnableCache && _cacheManager.Contains(GetCacheKey(key));
        }

        protected string GetCacheKey(string key)
        {
            return tableName + key;
        }


       

        protected static void ExecuteNonQuery(SqlDatabase sqldb, IDbTransaction transaction, string sql)
        {
            if (transaction == null)
            {
                sqldb.ExecuteNonQuery(CommandType.Text, sql);
            }
            else
                sqldb.ExecuteNonQuery((DbTransaction)transaction, CommandType.Text, sql);
        }

        protected static IDataReader ExecuteReader(SqlDatabase sqldb, IDbTransaction transaction, string sql)
        {
            var reader = transaction == null
                                     ? sqldb.ExecuteReader(CommandType.Text, sql)
                                     : sqldb.ExecuteReader((DbTransaction)transaction, CommandType.Text, sql);

            return reader;
        }
        protected static object ExecuteScalar(SqlDatabase sqldb, IDbTransaction transaction, string sql)
        {
            var value = transaction == null
                            ? sqldb.ExecuteScalar(CommandType.Text, sql)
                            : sqldb.ExecuteScalar((DbTransaction)transaction, CommandType.Text, sql);

            return value;
        }


        private static T ToModel(IDataRecord reader)
        {


            var t = new T();
            var pros = RepositoryReflectionCache.GetPropertyInfos(typeof(T));
            foreach (var pro in pros)
            {
                var value = reader[pro.Name];
                if ((value is DBNull) || value == null) continue;
                pro.FastSetValue(t, value);
            }
            return t;
        }

   
    }
}